package com.luxx.util;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DBUtil {

	private static final Log logger = LogFactory.getLog(DBUtil.class);

	private static Connection conn = null;

	private static String dbType = null;

	private static String driverName;

	private static String user;

	private static String password;

	private static String url;

	/**
	 * 获取数据库连接，默认连接MYSQL数据库
	 * 
	 * @author luxx
	 * @return Connection 数据库连接
	 * @throws Exception
	 */
	public static Connection getConnection() throws Exception {
		return getConnection(DBType.MYSQL.toString());
	}

	/**
	 * 获取数据库连接，默认MYSQL数据库
	 * 
	 * @param type
	 *            数据库类型（mySql，Oracle）
	 * @author luxx
	 * @return
	 * @throws Exception
	 */
	public static Connection getConnection(String type) throws Exception {
		if (type == null || type == "")
			type = DBType.MYSQL.toString();

		dbType = type;
		handleProperties(dbType);
		if (dbType.equals(type.toUpperCase())) {
			return getSQLConnection();
		}
		return null;
	}

	/**
	 * 连接数据库，返回数据库连接
	 * 
	 * @return Connection 数据库连接
	 * @throws Exception
	 */
	public static Connection getSQLConnection() throws Exception {
		if (null == conn) {
			// 保证可以正常读到配置文件
			if (driverName == null || driverName == "" || url == null || url == "" || user == null || user == ""
					|| password == null || password == "") {
				throw new Exception("读取配置文件失败");
			}
			Class.forName(driverName);
			conn = DriverManager.getConnection(url, user, password);
			logger.info("成功连接数据库！");
		}
		return conn;
	}

	/**
	 * 根据传入的类型参数，获取数据库配置文件
	 * 
	 * @author luxx
	 * @param type
	 *            传入的参数类型，通常为MYSQL,ORACLE等
	 * @author luxx
	 * 
	 */
	private static void handleProperties(String type) {
		Map<String, String> map = PropertiesUtil.readDatas(type.toLowerCase() + ".properties");
		driverName = map.get("driverName");
		url = map.get("url");
		user = map.get("user");
		password = map.get("password");
	}

	/**
	 * 通用的数据库连接关闭方法
	 * 
	 * @author luxx
	 *
	 */
	public static void close() {
		if (null != conn) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 获取目录名称，一般都为空。 在MYSQL中得到的Catalog是数据库名称列表，Schema是null
	 * 
	 * @param c
	 *            数据库连接
	 * @return
	 * @throws SQLException
	 */
	public static List getCatalogs(Connection c) throws SQLException {
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		rs = dmd.getCatalogs();
		List list = new LinkedList();
		while (rs.next()) {
			list.add(rs.getString(1));
		}
		return list;
	}

	/*
	 * Oracle和MySQL数据的组织结构是完全不同,直观表象上,表和视图的挂接途径不一样,在Oracle中,采取的是分用户管理机制,
	 * 表和视图挂接在某个用户下,此时用户会成为Oracle的一个”模式(schema)”；而在MySQL中表和视图是直接挂接在数据库下的。这样，
	 * 在Oralce中获取catalog得到的是null，获取schema得到大写的是用户名称列表。而在MySQL中得到的catalog是数据库名称列表
	 * ，而schema是null。
	 */

	/**
	 * 获取数据库名 ，在Oralce中获取Catalog得到的是null，获取Schema得到大写的是用户名称列表
	 * 
	 * @param c
	 *            数据库连接
	 * @return
	 * @throws SQLException
	 */
	public static Map getSchemas(Connection c) throws SQLException {
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		rs = dmd.getSchemas();
		Map map = new HashMap();
		List l;
		while (rs.next()) {
			String schema = rs.getString(1);
			String catalog = null;
			if (rs.getMetaData().getColumnCount() > 1) {
				catalog = rs.getString(2);
			}
			;
			l = (List) map.get(catalog);
			if (l == null) {
				l = new LinkedList();
				map.put(catalog, l);
			}
			l.add(schema);
		}
		return map;
	}

	/*
	 * ResultSet对象，有23列，详细的显示了表的类型： TABLE_CAT String => 表类别（可为 null） TABLE_SCHEM
	 * String => 表模式（可为 null） TABLE_NAME String => 表名称 COLUMN_NAME String => 列名称
	 * DATA_TYPE int => 来自 java.sql.Types 的 SQL 类型 TYPE_NAME String =>
	 * 数据源依赖的类型名称，对于 UDT，该类型名称是完全限定的 COLUMN_SIZE int => 列的大小。 BUFFER_LENGTH
	 * 未被使用。 DECIMAL_DIGITS int => 小数部分的位数。对于 DECIMAL_DIGITS不适用的数据类型，则返回 Null。
	 * NUM_PREC_RADIX int => 基数（通常为 10 或 2） NULLABLE int =>是否允许使用 NULL。
	 * columnNoNulls - 可能不允许使用 NULL 值 columnNullable - 明确允许使用 NULL值
	 * columnNullableUnknown - 不知道是否可使用 null REMARKS String => 描述列的注释（可为 null）
	 * COLUMN_DEF String => 该列的默认值，当值在单引号内时应被解释为一个字符串（可为 null） SQL_DATA_TYPE int
	 * => 未使用 SQL_DATETIME_SUB int => 未使用 CHAR_OCTET_LENGTH int => 对于
	 * char类型，该长度是列中的最大字节数 ORDINAL_POSITION int => 表中的列的索引（从 1 开始） IS_NULLABLE
	 * String => ISO 规则用于确定列是否包括 null。 YES --- 如果参数可以包括 NULL NO ---
	 * 如果参数不可以包括NULL 空字符串 --- 如果不知道参数是否可以包括 null SCOPE_CATLOG String
	 * =>表的类别，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） SCOPE_SCHEMA String
	 * =>表的模式，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） SCOPE_TABLE String
	 * =>表名称，它是引用属性的作用域（如果 DATA_TYPE 不是 REF，则为 null） SOURCE_DATA_TYPE short
	 * =>不同类型或用户生成 Ref 类型、来自 java.sql.Types 的 SQL 类型的源类型（如果 DATA_TYPE 不是
	 * DISTINCT 或用户生成的 REF，则为 null） IS_AUTOINCREMENT String => 指示此列是否自动增加 YES
	 * ---如果该列自动增加 NO --- 如果该列不自动增加
	 */

	/**
	 * 根据数据库连接、目录、数据库名以及表名获取表
	 * 
	 * @param c
	 *            数据库连接
	 * @param catalog
	 *            目录名称
	 * @param schema
	 *            数据库名
	 * @param tablePattern
	 *            表名
	 * @return
	 * @throws SQLException
	 */
	public static List getTables(Connection c, String catalog, String schema, String tablePattern) throws SQLException {
		logger.debug("catalog='" + catalog + "'");
		logger.debug("schema='" + schema + "'");
		logger.debug("table='" + tablePattern + "'");
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		// 类型包括表、视图、指针和别名
		rs = dmd.getTables(catalog, schema, tablePattern, new String[] { "TABLE", "VIEW", "SYNONYM", "ALIAS" });
		List list = new LinkedList();
		while (rs.next()) {
			list.add(rs.getString(3));
		}
		return list;
	}

	/**
	 * 获得表的外键
	 * 
	 * @param c
	 *            数据库连接
	 * @param catalog
	 * @param schema
	 * @param table
	 * @return
	 * @throws SQLException
	 */
	public static Set getForeignKeyColumns(Connection c, String catalog, String schema, String table)
			throws SQLException {
		logger.debug("catalog='" + catalog + "'");
		logger.debug("schema='" + schema + "'");
		logger.debug("table='" + table + "'");
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		rs = dmd.getImportedKeys(catalog, schema, table);
		HashSet columns = new HashSet();
		while (rs.next()) {
			columns.add(rs.getString(8));
		}
		return columns;
	}

	/**
	 * 获得表的主键
	 * 
	 * @param c
	 *            数据库连接
	 * @param catalog
	 * @param schema
	 * @param table
	 * @return
	 * @throws SQLException
	 */
	public static List getPrimaryKeyColumns(Connection c, String catalog, String schema, String table)
			throws SQLException {
		logger.debug("catalog='" + catalog + "'");
		logger.debug("schema='" + schema + "'");
		logger.debug("table='" + table + "'");
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		rs = dmd.getPrimaryKeys(catalog, schema, table);
		List pkColumns = new LinkedList();
		while (rs.next()) {
			List tmp = getTableColumns(c, catalog, schema, table, rs.getString(4));
			Column pkColumn = (Column) tmp.get(0);
			pkColumns.add(pkColumn);
		}
		return pkColumns;

	}

	/**
	 * 获取表的字段列表
	 * 
	 * @param c
	 *            数据库连接
	 * @param catalog
	 * @param schema
	 * @param table
	 * @return
	 * @throws SQLException
	 */
	public static List getTableColumns(Connection c, String catalog, String schema, String table) throws SQLException {
		return getTableColumns(c, catalog, schema, table, null);
	}

	/**
	 * 获取表的字段列表
	 * 
	 * @param c
	 * @param catalog
	 * @param schema
	 * @param table
	 * @param columnPattern
	 * @return
	 * @throws SQLException
	 */
	public static List getTableColumns(Connection c, String catalog, String schema, String table, String columnPattern)
			throws SQLException {
		logger.debug("catalog='" + catalog + "'");
		logger.debug("schema='" + schema + "'");
		logger.debug("table='" + table + "'");
		logger.debug("column='" + columnPattern + "'");
		DatabaseMetaData dmd = c.getMetaData();
		ResultSet rs = null;
		rs = dmd.getColumns(catalog, schema, table, columnPattern);
		List columns = new LinkedList();
		while (rs.next()) {
			Column aCol = new Column();
			aCol.sqlTypeName = rs.getString(6);  
            aCol.defalutValue = rs.getString(13);  
            aCol.name = rs.getString(4);  
            aCol.sqlType = rs.getShort(5);  
            aCol.sqlColumnLength=rs.getInt(7);  
            aCol.sqlDecimalLength=rs.getInt(9);  
            aCol.sqlNotNull = ("NO".equals(rs.getString(18)));  
            aCol.javaType = getJavaType(aCol.sqlType,aCol.sqlColumnLength,aCol.sqlDecimalLength);
			columns.add(aCol);
		}
		return columns;

	}

	/**
	 * 根据数据库数据类型，数据库字段长度和数据库小数位数判断数据库的长度
	 * 
	 * @param sqlType
	 *            数据库数据类型
	 * @param columnSize
	 *            数据库字段长度
	 * @param decimalDigits
	 *            小数部分位数
	 * @return
	 */
	public static Class getJavaType(int sqlType, int columnSize, int decimalDigits) {
		logger.debug("sqlType=" + sqlType);
		logger.debug("columnSize=" + columnSize);
		logger.debug("decimalDigits=" + decimalDigits);
		Class rv = String.class;
		if (sqlType == Types.CHAR || sqlType == Types.VARCHAR) {
			rv = String.class;
		} else if (sqlType == Types.FLOAT || sqlType == Types.REAL) {
			rv = Float.class;
		} else if (sqlType == Types.INTEGER) {
			rv = Integer.class;
		} else if (sqlType == Types.DOUBLE) {
			rv = Double.class;
		} else if (sqlType == Types.DATE) {
			// rv = java.util.Date.class;
			rv = String.class;
		} else if (sqlType == Types.TIMESTAMP) {
			// rv = java.util.Date.class;
			rv = String.class;
		} else if (sqlType == Types.TIME) {
			// rv = java.util.Date.class;
			rv = String.class;
		}
		// commented to support JDK version < 1.4
		/*
		 * else if (sqlType == Types.BOOLEAN) { rv = Boolean.class; }
		 */
		else if (sqlType == Types.SMALLINT) {
			rv = Short.class;
		} else if (sqlType == Types.BIT) {
			// rv = Byte.class;
			rv = Integer.class;
		} else if (sqlType == Types.BIGINT) {
			rv = Long.class;
		} else if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) {
			if (decimalDigits == 0) {
				if (columnSize == 1) {
					// rv = Byte.class;
					rv = Integer.class;
				} else if (columnSize < 5) {
					rv = Short.class;
				} else if (columnSize < 10) {
					rv = Integer.class;
				} else {
					rv = Long.class;
				}
			} else {
				if (columnSize < 9) {
					rv = Float.class;
				} else {
					rv = Double.class;
				}
			}
		}
		return rv;
	}

}
